import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_graphics_view/src/policy/base_policy_set.dart';

mixin SceneControlPolicy on BasePolicySet {
  AnimationController? _animationController;

  double _baseScale = 1.0;

  Offset _basePosition = Offset(0, 0);

  Offset _lastFocalPoint = Offset(0, 0);

  Offset transformPosition = Offset(0, 0);

  double transformScale = 1.0;

  bool canUpdateSceneModel = false;

  getAnimationController() {
    return _animationController;
  }

  setAnimationController(AnimationController controller) {
    _animationController = controller;
  }

  disposeAnimationController() {
    _animationController?.dispose();
  }

  onSceneScaleStart(ScaleStartDetails details) {
    _baseScale = sceneReader.state.scale;
    _basePosition = sceneReader.state.position;
    _lastFocalPoint = details.focalPoint;
  }

  onSceneScaleUpdate(ScaleUpdateDetails details) {
    if (canUpdateSceneModel) {
      _animationController?.repeat();
      _updateSceneModelWithLastValues();
      double previousScale = transformScale;

      transformPosition += details.focalPoint - _lastFocalPoint;
      transformScale = keepScaleInBounds(details.scale, _baseScale);

      var focalPoint = (details.localFocalPoint - transformPosition);
      var focalPointScaled = focalPoint * (transformScale / previousScale);

      _lastFocalPoint = details.focalPoint;
      transformPosition += focalPoint - focalPointScaled;

      _animationController?.reset();
    }
  }

  onSceneScaleEnd(ScaleEndDetails details) {
    if (canUpdateSceneModel) {
      _updateSceneModelWithLastValues();
    }
    _animationController?.reset();

    transformPosition = Offset(0, 0);
    transformScale = 1.0;

    sceneWriter.state.updateScene();
  }

  onScenePointerSignal(PointerSignalEvent event) {
    if (event is PointerScrollEvent) {
      double scaleChange = event.scrollDelta.dy < 0
          ? (1 / sceneReader.state.mouseScaleSpeed)
          : sceneReader.state.mouseScaleSpeed;
      scaleChange = keepScaleInBounds(scaleChange, sceneReader.state.scale);

      if (scaleChange == 0.0) {
        return;
      }

      double previoesScale = sceneReader.state.scale;

      sceneWriter.state.updateScale(scaleChange);

      var focalPoint = (event.localPosition - sceneReader.state.position);
      var focalPointScaled =
          focalPoint * (sceneReader.state.scale / previoesScale);

      sceneWriter.state.updatePosition(focalPoint - focalPointScaled);
      sceneWriter.state.updateScene();
    }
  }

  double keepScaleInBounds(double scale, double sceneScale) {
    double scaleResult = scale;
    if (scale * sceneScale <= sceneReader.state.minScale) {
      scaleResult = sceneReader.state.minScale / sceneScale;
    }
    if (scale * sceneScale >= sceneReader.state.maxScale) {
      scaleResult = sceneReader.state.maxScale / sceneScale;
    }

    return scaleResult;
  }

  _updateSceneModelWithLastValues() {
    sceneWriter.state
        .setPosition((_basePosition * transformScale) + transformPosition);
    sceneWriter.state.setScale(transformScale * _baseScale);
    canUpdateSceneModel = false;
  }
}

mixin SceneMovePolicy on BasePolicySet implements SceneControlPolicy {
  AnimationController? _animationController;

  Offset _basePosition = Offset(0, 0);

  Offset _lastFocalPoint = Offset(0, 0);

  Offset transformPosition = Offset(0, 0);

  double transformScale = 1.0;

  bool canUpdateSceneModel = false;

  getAnimationController() {
    return _animationController;
  }

  setAnimationController(AnimationController controller) {
    _animationController = controller;
  }

  disposeAnimationController() {
    _animationController?.dispose();
  }

  @override
  onSceneScaleStart(ScaleStartDetails details) {
    _basePosition = sceneReader.state.position;
    _lastFocalPoint = details.focalPoint;
  }

  @override
  onSceneScaleUpdate(ScaleUpdateDetails details) {
    if (canUpdateSceneModel) {
      _animationController?.repeat();
      _updateSceneModelWithLastValues();
      transformPosition += details.focalPoint - _lastFocalPoint;
      _lastFocalPoint = details.focalPoint;
      _animationController?.reset();
    }
  }

  @override
  onSceneScaleEnd(ScaleEndDetails details) {
    if (canUpdateSceneModel) {
      _updateSceneModelWithLastValues();
    }
    _animationController?.reset();

    transformPosition = const Offset(0, 0);

    sceneWriter.state.updateScene();
  }

  @override
  _updateSceneModelWithLastValues() {
    sceneWriter.state.setPosition(_basePosition + transformPosition);
    canUpdateSceneModel = false;
  }

  @override
  onScenePointerSignal(PointerSignalEvent event) {}

  @override
  double keepScaleInBounds(double scale, double sceneScale) {
    return 1.0;
  }
}
